﻿######################################
#                                    #
#  AutoArchiveManager - By Yingest   #
#                                    #
#The core class for AutoArchiveManger#
#                                    #
######################################

#! python3.13
#for General
import os
import time
import shutil
import re
import sys
from .simple_localization import local
#for logging
import logging
import functools
import traceback
from logging.handlers import RotatingFileHandler
#for yaml and cmd loading
import getopt
import yaml
from pathlib import Path
from dataclasses import dataclass
#for GUI
from tkinter import filedialog, simpledialog, messagebox
#for Functions
from .CCTasks import CCTasks
from .Sevenzip import ArchiveTask


# Customer Failure moe --------------------------------------------------
class LogManagerError(Exception):
    """Basic LogManager Error"""

    def __init__(self, message, original_exception=None):
        super().__init__(message)
        self.original_exception = original_exception
        self.timestamp = time.strftime('[%H:%M:%S]')
        # Get the line number of the error
        if original_exception:
            tb = traceback.extract_tb(original_exception.__traceback__)
            if tb:
                self.error_line = tb[-1].lineno
            else:
                self.error_line = None
        else:
            self.error_line = None

    def __str__(self):
        if self.original_exception:
            error_line_info = f" at line {self.error_line}" if self.error_line else ""
            return f"{self.timestamp} - {super().__str__()}{error_line_info} (Original: {type(self.original_exception).__name__}: {str(self.original_exception)})"
        return f"{self.timestamp} - {super().__str__()}"


class ConfigError(LogManagerError):
    """Configuration Error"""
    pass


def timed(func):
    """record the time of function"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        logger = logging.getLogger('ArchiveManager')
        try:
            result = func(*args, **kwargs)
            duration = time.time() - start_time
            logger.debug(f"[PERF] {func.__name__} executed in {duration:.2f}s")
            return result
        except Exception as e:
            duration = time.time() - start_time
            logger.error(
                f"[PERF-ERROR] {func.__name__} failed after {duration:.2f}s")
            raise

    return wrapper


# Logger Configure --------------------------------------------------------
def configure_logging(log_dir: Path, debug: bool = False):
    """Config Logging system"""
    log_dir.mkdir(parents=True, exist_ok=True)
    # Define Formatters
    formatter_f_iwec = logging.Formatter(
        '[%(asctime)s] [%(levelname)-4s]   %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S')
    formatter_debug = logging.Formatter(
        '>>  [%(asctime)s] [%(levelname)s] %(module)s: %(lineno)d - %(message)s',
        datefmt='%H:%M:%S')
    formatter_c_i = logging.Formatter('[%(asctime)s]  %(message)s',
                                      datefmt='%H:%M:%S')
    formatter_c_wec = logging.Formatter(
        '[%(asctime)s]  [%(levelname)-4s] %(message)s', datefmt='%H:%M:%S')
    # Special formater
    formatter_orig = logging.Formatter('\n%(message)s')
    formatter_title = logging.Formatter('--%(message)s>----------------------')
    formatter_end = logging.Formatter('%(message)s')

    # Custom filters to apply different formats based on log level
    class LFFilter(logging.Filter):

        def __init__(self, level, formatter, skip_console=False):
            self.level = level
            self.formatter = formatter
            self.skip_console = skip_console  # the flag to skip console handler
            super().__init__()

        def filter(self, record):
            if record.levelno == self.level:
                record.formatter = self.formatter
            if record.getMessage().startswith("->"):
                record.formatter = formatter_orig
            elif record.getMessage().startswith('-->'):
                record.formatter = formatter_title
            elif record.getMessage() == '':
                record.formatter = formatter_end
            if hasattr(record, 'file_only') and record.file_only:
                return not self.skip_console
            return True

    # File Log Handler _ By Day
    file_handler = RotatingFileHandler(
        log_dir / f'Archive_Manager_{time.strftime("%Y-%m-%d")}.log',
        maxBytes=10 * 1024 * 1024,  # 10MB
        backupCount=30,
        encoding='utf-8')
    file_handler.setLevel(logging.INFO)
    file_handler.addFilter(LFFilter(logging.DEBUG, formatter_debug))
    file_handler.addFilter(LFFilter(logging.INFO, formatter_f_iwec))
    file_handler.addFilter(LFFilter(logging.WARN, formatter_f_iwec))
    file_handler.addFilter(LFFilter(logging.ERROR, formatter_f_iwec))
    file_handler.addFilter(LFFilter(logging.CRITICAL, formatter_f_iwec))

    # Console Log Handler
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG if debug else logging.INFO)
    console_handler.addFilter(LFFilter(logging.DEBUG, formatter_debug))
    console_handler.addFilter(
        LFFilter(logging.INFO, formatter_c_i, skip_console=True))
    console_handler.addFilter(LFFilter(logging.WARN, formatter_c_wec))
    console_handler.addFilter(LFFilter(logging.ERROR, formatter_c_wec))
    console_handler.addFilter(LFFilter(logging.CRITICAL, formatter_c_wec))

    # format the message
    class CustomFormatter(logging.Formatter):

        def format(self, record):
            if hasattr(record, 'formatter'):
                formatter = record.formatter
            else:
                formatter = self._fmt
            if formatter == formatter_title:
                original_msg = record.getMessage()
                trimmed_msg = original_msg[3:]
                width = 0
                for char in trimmed_msg:
                    if ord(char) > 127: width += 2
                    else: width += 1
                padding_length = 30 - width  # fixed width of 30 characters
                if padding_length > 0:
                    padded_msg = trimmed_msg + ' ' * padding_length
                else:
                    padded_msg = trimmed_msg
                record.msg = '-->' + padded_msg
            return formatter.format(record)

    custom_formatter = CustomFormatter()
    file_handler.setFormatter(custom_formatter)
    console_handler.setFormatter(custom_formatter)
    # Master Logger Configuration
    logger = logging.getLogger()
    logger.setLevel(logging.NOTSET)
    logger.handlers = [file_handler, console_handler]


# Config Manager --------------------------------------------------------
@dataclass(frozen=True)
class AppConfig:
    """App config class"""
    machine: str
    directory: Path
    zipArgvs: list[str]
    cc_t: int
    zip_t: int
    wait_t: int
    zip_qty: int
    listDepth: int
    cleanDepth: int
    saveDepth: int
    ccTasks: dict[list[dict[str, str, int, list[str], str]], int]
    debug: bool = False

    @classmethod
    def loading(cls, config_path: Path, argv: str) -> 'AppConfig':
        """Load Config data from YAML file"""
        ### Read YAML Files
        try:
            with config_path.open() as f:
                setup = yaml.safe_load(f)
        except (KeyError, yaml.YAMLError) as e:
            raise ConfigError(f"[YAML] Read Config File Fail: {str(e)}",
                              original_exception=e)
        ### Configuration Validation
        required_keys = ['task_name', 'cc_zip_waitTime', 'zip_qty', 'machines']
        for key in required_keys:
            if key not in setup:
                raise ConfigError(f"[YAML] Missing required key: {key}")
        # Verify Machine Configuration
        if not isinstance(setup['machines'], list) or len(
                setup['machines']) == 0:
            raise ConfigError(f"[YAML] Invalid machines configuration")
        # Verify the Machine config
        for machine in setup['machines']:
            if not all(
                    k in machine for k in
                ['machine', 'directory', 'list_clean_saveDepth', 'ccTasks']):
                raise ConfigError(f"[YAML] Invalid machine config: {machine}")

        # Loading Configuration from Command Line Parameters
        cls.task_name = setup['task_name']
        cls.cc_t, cls.zip_t, cls.wait_t = setup['cc_zip_waitTime']
        cls.zip_qty = setup['zip_qty']
        machines = setup['machines']
        try:
            opts, _ = getopt.getopt(
                argv, "n:m:d:t:q:z:l:ch",
                ["name=", "machine=", "directory=", "debug"])
        except getopt.GetoptError as e:
            raise ConfigError(f"[Argv] Wrong Input->{e.opt}: {e}")
        # Zip default Argues; please refer to https://documentation.help/7-Zip-18.0/add1.html
        cls.zipArgvs = ['-t7z', '-mx5', '-mmt2']
        try:
            for opt, arg in opts:
                match opt:
                    case '-n' | '--name':
                        cls.task_name = arg
                    case '-m' | '--machine':
                        mType = int(arg)
                    case '-d' | '--directory':
                        root_dir = Path(arg)
                        if not cls._dir_check(root_dir):
                            raise ValueError("Invalid directory")
                    case '--debug':
                        cls.debug = True
                    case '-t':  #format is '-t-c10-z100-w5' or '-tc10 -tz100'
                        ars = arg.split('-')
                        for ar in ars:
                            if ar.startswith('c'): cls.cc_t = int(ar[1:])
                            elif ar.startswith('z'): cls.zip_t = int(ar[1:])
                            elif ar.startswith('w'): cls.wait_t = int(ar[1:])
                    case '-q':
                        cls.zip_qty = int(arg)
                    case '-z':  # format is '-z-mmt2-mx5' or '-zmmt2 -zmx5'
                        ars = arg.split('-')
                        cls._zip_Args(cls, ars)
                    case '-l':
                        local.change_language(arg)
                    case '-c':
                        cls._CleanTemp()
                    case '-h':
                        return print(local['AppC_Help'])
        except Exception as e:
            raise ConfigError(f"[Argv] Wrong Input->{opt}<XX>: {e}",
                              original_exception=e)
        invalid_chars = r'[<>:"/\\|?*\x00-\x1F]'
        cls.task_name = re.sub(invalid_chars, '', cls.task_name)
        if cls.task_name == '':
            cls.task_name = "General"
        mlists = local['AppC_SelC'] % (len(machines) - 1, " " * 60)
        for i, man in enumerate(machines):
            mlists += "    %d  ->   %s;\n" % (i, man['machine'])
        while True:
            if 'mType' not in locals():
                mType = simpledialog.askinteger(local['AppC_SelT'], mlists)
            else:
                if mType is None: return
                if not (0 <= mType <
                        len(machines)):  # Check the machine input is valid
                    messagebox.showwarning(local['AppC_WarT'],
                                           local['AppC_SelErr'])
                    mType = simpledialog.askinteger(local['AppC_SelT'], mlists)
                else:
                    cls.machine = machines[mType]['machine']
                    cls.directory = Path(machines[mType]['directory'])
                    cls.listDepth, cls.cleanDepth, cls.saveDepth = machines[
                        mType]['list_clean_saveDepth']
                    cls.ccTasks = machines[mType]['ccTasks']
                    break

        if 'root_dir' in locals(): cls.directory = root_dir

        if not (os.path.exists(cls.directory)
                and os.path.isdir(cls.directory)):
            root_dir = filedialog.askdirectory()
            if root_dir == "": return
        try:
            if not (cls.listDepth >= cls.cleanDepth >= cls.saveDepth):
                raise ValueError("list_depth>=clean_depth>=save_depth")
            tasks = cls.ccTasks["Tasks"]
            markDepth = cls.ccTasks["markDepth"]
            if int(markDepth) < 0:
                raise ValueError("markDepth >= 0")
            ccnameTemp = []
            for cc in tasks:
                if cc["name"] == "":
                    raise ValueError("ccTask name shoud not be empty")
                elif not cc["name"] == re.sub(invalid_chars, '', cc["name"]):
                    raise ValueError(
                        "ccTask name shoud not be with invalid chars")
                else:
                    if cc["name"] in ccnameTemp:
                        raise ValueError(
                            "ccTask name shoud be unic for each task")
                    ccnameTemp.append(cc["name"])
                if not cc["action"] in ["co", "cl", "cc", "cv"]:
                    raise ValueError("ccTask action in [cv,co,cl,cc]")
                if cc['action'] != "cl" and (not cls._dir_check(
                        Path(cc['argv']))):
                    raise ValueError("valid targetPath for clTask")
                if cc['action'] == "cv" and (not cc['argv']
                                             in [".bmp", ".png", ".jpeg"]):
                    raise ValueError("Valid format for cvTask")
                if not cc['martch']:
                    raise ValueError("Martch is not empty")
                if len(cc['martch']) < int(markDepth):
                    raise ValueError("markDepth <= len(match)")
        except ValueError as e:
            raise ConfigError(
                f"[Config] Invalid parameter input; Require: {e}",
                original_exception=e)
        return cls

    def _dir_check(dir: Path):
        """Check if it is a directory"""
        if (os.path.exists(dir) and os.path.isdir(dir)) or dir != Path(''):
            return True
        else:
            return False

    def _zip_Args(cls, ars: list[str]):
        """To be edit if you modify default cls:zipArgvs"""
        for ar in ars:
            if ar != "":
                arg = '-' + ar
                if arg[0:2] == '-t': cls.zipArgvs[0] = arg
                elif arg[0:3] == '-mx': cls.zipArgvs[1] = arg
                elif arg[0:4] == '-mmt': cls.zipArgvs[2] = arg
                else: cls.zipArgvs.append(arg)
        return

    def _CleanTemp():
        """Clean the temp folder of the system"""
        deleteFileCount = 0
        deleteFolderCount = 0
        folder = 'C:/Users/' + os.getlogin() + '/AppData/Local/Temp'
        print(local['TempC_Start'])
        for the_file in os.listdir(folder):
            file_path = os.path.join(folder, the_file)
            indexNo = file_path.find('\\')
            itemName = file_path[indexNo + 1:]
            try:
                if os.path.isfile(file_path):
                    os.unlink(file_path)
                    print('' % itemName)
                    deleteFileCount = deleteFileCount + 1
                elif os.path.isdir(file_path):
                    if file_path.__contains__('chocolatey'): continue
                    shutil.rmtree(file_path)
                    print(f"[CT]  [DEL]  {itemName}")
                    deleteFolderCount = deleteFolderCount + 1
            except Exception as e:
                print(f"[CT]  [DEL]  [FAIL]{itemName}")
        print(local['TempC_Done'] % (deleteFileCount, deleteFolderCount))
        return


class LogFolderDir:
    """Class representing a log folder directory."""

    def __init__(self, cfg: AppConfig, relative_path: list[str],
                 logger: logging.Logger):
        """
        Initialize a LogFolderDir instance.

            Args:
                cfg (AppConfig): Cfg include root dir ,Zip time and SaveDepth
                relative_path (list[str]): The relative path of the log folder as a list of strings.
                Logger (logging.Logger): For record the Logger
            """
        self.new_name = '_'.join(relative_path)
        self.orig_path = os.path.join(cfg.directory, *relative_path)
        self.target_path = os.path.join(cfg.directory,
                                        *relative_path[:cfg.saveDepth])
        self.m_time = os.stat(self.orig_path).st_mtime
        self.logger = logger
        self.all_cld = True
        self.cc = CCTasks(self.orig_path, relative_path, logger)
        self.ac = ArchiveTask(cfg.zipArgvs, '_'.join(relative_path),
                              self.orig_path, self.target_path, logger)


class LogArchiveManager:
    """Core Class for auto archive."""

    def __init__(self, config_path: str, argv: str):
        """initialization Data

        Args:
            config_path (str): yaml config path
            argv (str): argument from cmd

        """
        print("""
    -<-<-<-<                                >->->->-  
  ->->->->    Auto Archive Tool is RUNNING    <-<-<-<-
    -<-<-<-<                  -- by Yingest >->->->-
    """)
        self.started = False
        try:
            self.cfg = AppConfig.loading(Path(config_path), argv)
        except Exception as e:
            print(f"Error: {e}")
            time.sleep(5)
            sys.exit(1)
        if self.cfg is None:
            print(local['AppC_SelCan'])
            sys.exit(0)
        new_title = f"Auto Archive Tool - {self.cfg.machine} - {self.cfg.directory}"
        os.system(f"title {new_title}")
        self.lockTXT = os.path.join(
            self.cfg.directory,
            local['LogA_LockTXT'] + ".processinglock.By_Yingest.txt")
        configure_logging(
            Path(os.getcwd()) / 'Logs' / self.cfg.task_name, self.cfg.debug)
        self.logger = logging.getLogger('ArchiveManager')
        self.all_CCed = False

    def __enter__(self):
        """Context manager entrance"""
        if os.path.exists(self.lockTXT):
            try:
                os.remove(self.lockTXT)
            except OSError:
                messagebox.showwarning("⚠︎", local['LogA_ConflictErr'])
                self.logger.warning(local['LogA_ConflictErr'])
                sys.exit(2)
        self.logger.debug(f"""Debug mode enabled.
machine info:
    Machine: {self.cfg.machine}
    Directoy: {self.cfg.directory}
    ListDepeh: {self.cfg.listDepth}
    CleanDepth:{self.cfg.cleanDepth}
    SaveDepth: {self.cfg.saveDepth}
    ZipTime: {self.cfg.zip_t}
    ZipQTY: {self.cfg.zip_qty}
    CCTime: {self.cfg.cc_t}
    CCTasks:{self.cfg.ccTasks}""")
        self.logger.info(
            "->->->->    Auto Archive Tool is RUNNING -By Yingest    <-<-<-<-",
            extra={"file_only": True})
        self.logger.info(local['LogA_Start'])
        self.logger.info(f"{local['LogA_SeldM']} {self.cfg.machine}")
        self.logger.info(f"{local['LogA_SeldD']} {self.cfg.directory}")
        with open(self.lockTXT, 'w', encoding='utf-8') as fs:
            fs.write(local['LogA_LockC'])
        self.locker = open(self.lockTXT, 'a+', encoding='utf-8')
        self.folder_list = self._get_all_folders()
        if len(self.folder_list) == 0:
            self.logger.info(local['LogA_NoF'])
            self.locker.close()
            os.remove(self.lockTXT)
        else:
            self.logger.info(local['LogA_GOF'] % (len(self.folder_list)))
            self.started = True
        return self

    def __exit__(self, exc_type, *_):
        """context manager exit"""
        if self.started:
            self.locker.close()
            os.remove(self.lockTXT)
        if exc_type is None:
            self.logger.info(local['LogA_Done'])
        elif exc_type == KeyboardInterrupt:
            print("")
            self.logger.info(local['LogA_Interrupt'])
        else:
            self.logger.error(
                f"Error: {exc_type.__name__} - {exc_type.__doc__}")
            self.logger.info(local['LogA_DoneIssue'])
        if self.cfg.wait_t > 0:
            print(local['LogA_DoneWait'] % self.cfg.wait_t)
            time.sleep(self.cfg.wait_t)
        self.logger.info('')

    def _get_all_folders(self,
                         working_dir="",
                         rel_path=None,
                         depth=1) -> list[LogFolderDir]:
        """Traverse all folders in the path."""
        if rel_path is None: rel_path = []
        if depth > self.cfg.listDepth:
            return []
        folders = sorted(
            os.listdir(working_dir if depth > 1 else self.cfg.directory))
        folder_list = []
        for folder in folders:
            re_path_add = rel_path + [folder]
            root_add_folder = os.path.join(
                working_dir if depth > 1 else self.cfg.directory, folder)
            if os.path.isfile(root_add_folder):
                continue
            if depth < self.cfg.listDepth:
                folder_list.extend(
                    self._get_all_folders(root_add_folder, re_path_add,
                                          depth + 1))
            else:
                folder_list.append(
                    LogFolderDir(self.cfg, re_path_add, self.logger))

        return folder_list

    def _flush_locker(self, updates: str) -> None:
        self._checkstarted()
        self.locker.write(updates)
        self.locker.flush()
        return

    def _checkstarted(self):
        if not self.started:
            raise ValueError("Error: LogArchiveManager have not been started.")

    @timed
    def ccTasks(self) -> None:
        """CopyClean or Convert... the folder based on specified conditions."""
        self._checkstarted()
        self._flush_locker("\nCC Tasks Ongoing .........")
        if self.cfg.cc_t < 0:
            self.logger.warning(local['LogA_CCDisa1'])
            return
        self.logger.info(local['LogA_CCStart'])
        tasks = []
        for task in self.cfg.ccTasks['Tasks']:
            if task['name'].startswith('-'):
                self.logger.warning(local['LogA_CCDisa2'] % (task['name'][1:]))
            else:
                tasks.append([
                    task['name'], task['action'],
                    self.cfg.cc_t if self.cfg.cc_t > 0 else task['remaining'],
                    task['martch'], task['argv']
                ])
        markDepth = int(self.cfg.ccTasks['markDepth'])
        cced, skipped, all_cced = 0, 0, True
        for folder in self.folder_list:
            self._flush_locker(
                f"\n{time.strftime('[%H:%M:%S]', time.localtime())} {folder.orig_path}  ⋙"
            )
            self.logger.info("[CC]  [>>]  " + folder.orig_path)
            folder.cc.run(tasks, markDepth)
            if folder.cc.all_ccd and (folder.cc.ccd > 0 or folder.cc.skd > 0):
                folder.all_cld = all(folder.cc.all_ccd.values())
                self.logger.info(local['LogA_CCDone'] %
                                 (folder.cc.ccd, folder.orig_path))
                cced += folder.cc.ccd
                skipped += folder.cc.skd
                all_cced = all_cced and folder.all_cld
            self._flush_locker("  ✅")
        self.logger.info(local['LogA_CCSucc'] % (cced, self.cfg.cc_t / 60))
        if skipped > 0:
            self.logger.warning(local['LogA_CCSkip'] % skipped)
        if skipped > 0 or not all_cced:
            self.logger.warning(local['LogA_nAllCCed'])
        else:
            self.logger.info((local['LogA_AllCCed']) if cced > 0 else (
                local['LogA_CCZero']))

    @timed
    def archive(self):
        """ Archive the Logs to specified type """
        self._checkstarted()
        self._flush_locker("\nArchive Ongoing .........")
        zipmsg = "7-Zip (a) 24.09 (x86) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29"
        if self.cfg.zip_qty <= 0 or self.cfg.zip_t <= 0:
            self.logger.info(local['LogA_ArcDisale'])
            return 0
        self.logger.info(local['LogA_ArcStart'])
        count, i = 0, 0
        if self.folder_list: print(zipmsg)
        for folder in self.folder_list:
            if count >= self.cfg.zip_qty:
                break
            self.logger.info(
                local['LogA_ArcCount'] %
                (count + 1, self.cfg.zip_qty, i + 1, len(self.folder_list)))
            if not folder.all_cld or (time.time() -
                                      folder.m_time) <= self.cfg.zip_t:
                i += 1
                continue
            result = folder.ac.run(self._flush_locker)
            if result == 0:
                while True:
                    try:
                        if os.path.isfile(folder.orig_path):
                            os.remove(folder.orig_path)
                        else:
                            shutil.rmtree(folder.orig_path)
                    except:
                        self.logger.error(local['LogF_DelErr'] %
                                          folder.orig_path)
                        messagebox.showwarning(
                            "⚠︎", local['LogF_DelErr'] % folder.orig_path)
                        continue
                    break
                self._flush_locker("  ✅")
                self.logger.info(f"[AR]  [Done]  {folder.ac.new_path}")
                count += 1
                i += 1
            else:
                if result == -88:
                    self.logger.warning(local['LogA_ConflictErr'])
                    messagebox.showwarning("⚠︎", local['LogA_ConflictErr'])
                else:
                    self.logger.error(f"[AR]  [Error]  {folder.ac.new_path}")
                    self.logger.error(f"Zip Error Occurred: {result}")
                    self._flush_locker(f"⚠︎{result}")
                self.logger.error(local['LogA_ArcStop'])
                break
        self.logger.info(local['LogA_ArcZero'] if count ==
                         0 else local['LogA_ArcSucc'] % count)
        return count

    def remove_empty(self, working_dir="", rel_path=None, depth=1):
        """ Delete empty folders after Archving. """
        self._checkstarted()
        if rel_path is None: rel_path = []
        if depth > self.cfg.listDepth:
            return

        folders = sorted(
            os.listdir(working_dir if depth > 1 else self.cfg.directory))
        for folder in folders:
            re_path_add = rel_path + [folder]
            root_add_folder = os.path.join(
                working_dir if depth > 1 else self.cfg.directory, folder)
            if os.path.isfile(root_add_folder):
                continue
            if depth <= self.cfg.listDepth:
                self.remove_empty(root_add_folder, re_path_add, depth + 1)
                full_path = os.path.join(self.cfg.directory, *re_path_add)
                if depth > self.cfg.cleanDepth and not os.listdir(full_path):
                    while True:
                        try:
                            shutil.rmtree(full_path)
                        except Exception as e:
                            self.logger.error(
                                f"{local['LogA_REerror'] % full_path}{e}")
                            messagebox.showwarning(
                                "⚠︎", local['LogA_REerror'] % full_path)
                        if not Path(full_path).exists():
                            self.logger.info(f"[RE]  [DEL]: {full_path}")
                            break
        return

    def run(self):
        """Run the whole process"""
        if self.started:
            self.ccTasks()
            self.archive()
            self.remove_empty()
        return


if __name__ == "__main__":
    argv = "-m2  -tc2-z3-w3 -q1  -lzh_cn"
    with LogArchiveManager("machines.yaml", argv) as lam:
        lam.run()
